#pragma once

#include "Reflection/Enum.h"

#include "Reflection/Property.h"
#include "Reflection/PropertyList.h"

#include "Reflection/TypeInstance.h"


class CNativeEnumValueGetSet : public IStaticProperty 
{
public:
	CNativeEnumValueGetSet(void* InValue,CEnumBase* InEnumType)
        :Value(InValue)
        ,EnumType(InEnumType)
    {}

	virtual CAny GetValue() override
    {
        return CAny(EnumType,EnumType->GetValueType()->CloneInstance(Value));
    }
	virtual void SetValue(const CAny& InValue) 
    {
        assert(false && "Can't change value of Enume");
    }

protected:
    void* Value;
    CEnumBase* EnumType=nullptr;
};


class CNativeEnumValueField : public IFieldProperty
{
public:
    CNativeEnumValueField(void* InValue,uint32_t InValueIndex)
        :Value(InValue)
        ,ValueIndex(InValueIndex)
        {}

	virtual const void* GetValuePointer(const void* InObjectPtr) override
    {
        assert(false&& "this is a static field,Invoke GetStaticValuePointer() instead");
        return nullptr;
    }
	virtual void* GetValuePointer( void* InObjectPtr) override
    {
        assert(false&& "this is a static field,Invoke GetStaticValuePointer() instead");
        return nullptr;
    }

	virtual uint32_t GetFieldIndex() override
    {
        return ValueIndex;
    }

	//is this a static field property
	virtual bool IsStatic() override
    {
        return true;
    }

	//get static field value address
	virtual void* GetStaticValuePointer() override
    {
        return Value;
    }

protected:
    void* Value;
    uint32_t ValueIndex;

};


class CNativeEnumValueProperty : public  CProperty
{
public:
    CNativeEnumValueProperty(
        CPropertyList * InList
        ,const CString& InName
        ,int64_t InValueIndex
        ,void* Value
        ,CEnumBase* InEnumType
        ,CType* InValueType
    )
    :CProperty(
		InList
		,InName
		,InValueType
		,{}
	    )
    {
        StaticProperty=new CNativeEnumValueGetSet(
            Value
            ,InEnumType
        );

        FieldProperty = new CNativeEnumValueField(
            Value
            ,InValueIndex
        );
    }

protected:

    int64_t InValueIndex;
};

template<typename T>
class CNativeEnum: public CEnumBase 
{
public:
    CNativeEnum(
        const CString& InFullName
        , const TMap<int64_t,CString>& InValues)
    :CEnumBase( 
        InValues
        ,STypeID(InFullName,true)
        ,ETypeFlags::None
        )
    {
        static_assert(std::is_enum<T>::value,"T must be enum");
        
        //create static fields for script
        Properties= new CPropertyList(this);
        Instancable= new CNativeEnumInstance(this);

        auto Values= InValues.Keys();
        AllEnumValues.Reserve(Values.Num());
        for(auto Value: Values)
        {
            AllEnumValues.Add((T)Value);
        }

        for(int64_t ValueIndex=0; ValueIndex<Values.Num();ValueIndex++ )
        {
            auto FieldValue=Values[ValueIndex];
            auto FieldName=InValues[FieldValue];

            auto Property= new CNativeEnumValueProperty(
                Properties
                ,FieldName
                ,ValueIndex
                ,AllEnumValues.GetDataPtr(ValueIndex)
                ,this
                ,GetValueType()
            );
            Properties->AddProperty(Property);
            
        }

    }
    
    virtual CType* GetValueType() const override
    {
        static CType* ValueType=  CGetType<std::underlying_type_t<T>>()();
        return ValueType;
    }

    //static fields 
    //because script treat enum  as a class, 
    //and treat enum value is a static field, so native enum
    //required to create static fields for each enum value
    TVector<T> AllEnumValues;

protected:
    class CNativeEnumInstance : public ITypeInstance
    {
    protected:
        CNativeEnum* Type;
    public:
        CNativeEnumInstance(CNativeEnum* InType)
            :Type(InType)
        {}

        bool IsForceConvertableTo(CType * Other) override
        {
            return Type->GetValueType()->IsForceConvertableTo(Other);
        }

        virtual uint32_t GetSize() override
        {
            return sizeof(T);
        }

        virtual void InitInstance(void * Memory) override
        {
            new (Memory) T();
        }

        virtual void EraseInstance(void * Instance) override
        {}

        virtual void CloneInstance(const void * Instance, void * Memory) override
        {
            new (Memory)T(*(T*)Instance);
        }

        virtual uint64_t GetInstanceHash(const void * Instance) const override
        {
            return (uint64_t)*(T*)Instance;
        }

        virtual bool IsInstanceEqual(const void * Instance0,const void * Instance1) const override
        {
            return *(T*)Instance0==*(T*)Instance1;
        }

        virtual CString ToDebugString(const void * InInstance) const override
        {
            return Type->GetFullName()+U"::"+Type->GetValueName(*(T*)InInstance);
        }

        virtual void *CreateInstance() override
        {
            return new T();
        }

        virtual void ResetAllocated(void * Memory) override
        {
            *reinterpret_cast<T*>(Memory)=T(0);
        }
    };
};